﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Notoric.Gui.Controls
{
    /// <summary>
    /// Adds a rubber band drawing functionality to <see cref="Canvas"/> control.
    /// </summary>
    public partial class RubberBandingCanvas : Canvas
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="RubberBandingCanvas"/> instance.
        /// </summary>
        public RubberBandingCanvas()
        {
            InitializeComponent();
        }

        #endregion

        #region Dependency Properties Fields

        /// <summary>
        /// Idenitifes the <see cref="DrawRubberBand"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty DrawRubberBandProperty = DependencyProperty.Register(
            "DrawRubberBand", typeof(bool), typeof(RubberBandingCanvas), new PropertyMetadata(true)
            );

        #endregion

        #region Routed Event Fields

        /// <summary>
        /// Identifies the <see cref="RubberBandDrawn"/> routed event.
        /// </summary>
        public static readonly RoutedEvent RubberBandDrawnEvent = EventManager.RegisterRoutedEvent(
            "RubberBandDrawn", RoutingStrategy.Bubble, typeof(EventHandler<RubberBandDrawnEventArgs>), typeof(RubberBandingCanvas));

        #endregion

        #region Events

        /// <summary>
        /// Occurs when the user completes drawing the rubber band (releases the mouse).
        /// </summary>
        public event EventHandler<RubberBandDrawnEventArgs> RubberBandDrawn
        {
            add { AddHandler(RubberBandDrawnEvent, value); }
            remove { RemoveHandler(RubberBandDrawnEvent, value); }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Raises the <see cref="RubberBandDrawn"/> routed event on this <see cref="RubberBandingCanvas"/> instance.
        /// </summary>
        /// <param name="left">Left coordinate of the rubber band</param>
        /// <param name="top">Top coordinate of the rubber band</param>
        /// <param name="width">Width of the rubber band</param>
        /// <param name="height">Height of the rubber band</param>
        protected void RaiseRubberBandDrawn(double left, double top, double width, double height)
        {
            RaiseEvent(
                new RubberBandDrawnEventArgs()
                {
                    Left = left,
                    Top = top,
                    Height = height,
                    Width = width
                }
            );
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Attaches a handler of the <see cref="RubberBandDrawn"/> routed event to a given <see cref="UIElement"/>.
        /// </summary>
        /// <param name="d">The object to which the event handler is attached (must derive from <see cref="UIElement"/>)</param>
        /// <param name="handler">The handler being attached</param>
        public static void AddRubberBandDrawnHandler(DependencyObject d, EventHandler<RubberBandDrawnEventArgs> handler)
        {
            UIElement uie = d as UIElement;
            if (uie != null)
                uie.AddHandler(RubberBandingCanvas.RubberBandDrawnEvent, handler);
        }

        /// <summary>
        /// Detaches a handler of the <see cref="RubberBandDrawn"/> routed event from a given <see cref="UIElement"/>.
        /// </summary>
        /// <param name="d">The object from which the event handler is detached (must derive from <see cref="UIElement"/>)</param>
        /// <param name="handler">The handler being detached</param>
        public static void RemoveRubberBandDrawnHandler(DependencyObject d, EventHandler<RubberBandDrawnEventArgs> handler)
        {
            UIElement uie = d as UIElement;
            if (uie != null)
                uie.RemoveHandler(RubberBandingCanvas.RubberBandDrawnEvent, handler);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the value indicating whether 
        /// the rubber band drawing is currently enabled.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the rubber band drawing is currently enabled.
        /// Default value is <see langword="true"/>.
        /// </para>
        /// </value>
        public bool DrawRubberBand
        {
            get { return (bool)GetValue(DrawRubberBandProperty); }
            set { SetValue(DrawRubberBandProperty, value); }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the user presses the left mouse button on the canvas.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the canvas has not captured the mouse yet it captures it and stored the coordinates of the mouse
        /// cursor to <see cref="bandStartPoint"/>.
        /// </remarks>
        /// <param name="sender">Object where the event hadnler is attached</param>
        /// <param name="e">Event data</param>
        private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!DrawRubberBand)
                return;

            if (!IsMouseCaptured)
            {
                bandStartPoint = e.GetPosition(this);
                CaptureMouse();
            }
        }

        /// <summary>
        /// Occurs when the user releases the left mouse button on the canvas.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the mouse is captured and the rubber band is visible, the handler raises the <see cref="RubberBandDrawn"/>
        /// routed event, hides the rubber band and releases the mouse capture.
        /// </remarks>
        /// <param name="sender">Object where the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (IsMouseCaptured && rubberBand.Visibility == Visibility.Visible)
            {
                RaiseRubberBandDrawn(
                    Canvas.GetLeft(rubberBand),
                    Canvas.GetTop(rubberBand),
                    rubberBand.Width,
                    rubberBand.Height
                    );
                rubberBand.Visibility = Visibility.Hidden;

                ReleaseMouseCapture();
            }
        }

        /// <summary>
        /// Occurs when the user moves the mouse over the canvas.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the mouse is currently captured, the handler computes the coordinate and size of the rubber band
        /// and updates it. The handler also ensures that the rubber band does not leave the canvas.
        /// </remarks>
        /// <param name="sender">Object where the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (IsMouseCaptured)
            {
                Point currentPoint = e.GetPosition(this);

                if (rubberBand.Visibility == Visibility.Hidden)
                    rubberBand.Visibility = Visibility.Visible;

                double width = Math.Abs(bandStartPoint.X - currentPoint.X);
                double height = Math.Abs(bandStartPoint.Y - currentPoint.Y);
                double left = Math.Min(bandStartPoint.X, currentPoint.X);
                double top = Math.Min(bandStartPoint.Y, currentPoint.Y);

                if (left < 0)
                    left = 0;
                if (top < 0)
                    top = 0;
                if (left + width > ActualWidth)
                    width -= (left + width) - ActualWidth;
                if (top + height > ActualHeight)
                    height -= (top + height) - ActualHeight;

                rubberBand.Width = width;
                rubberBand.Height = height;
                Canvas.SetLeft(rubberBand, left);
                Canvas.SetTop(rubberBand, top);
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Stores the point from which the user started to draw the rubber band.
        /// </summary>
        private Point bandStartPoint;

        #endregion
    }

    /// <summary>
    /// Holds the data of the <see cref="RubberBandingCanvas.RubberBandDrawn"/> routed event.
    /// </summary>
    public class RubberBandDrawnEventArgs : RoutedEventArgs
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="RubberBandDrawnEventArgs"/> instance.
        /// </summary>
        /// <remarks>
        /// The <see cref="RoutedEventArgs.RoutedEvent"/> field is automatically set to <see cref="RubberBandingCanvas.RubberBandDrawnEvent"/>.
        /// </remarks>
        public RubberBandDrawnEventArgs()
            : base(RubberBandingCanvas.RubberBandDrawnEvent)
        {
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the left coordinate of the rubber band.
        /// </summary>
        /// <value>
        /// The left coordinate of the rubber band in device independent pixels.
        /// </value>
        public double Left
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the top coordinate of the rubber band.
        /// </summary>
        /// <value>
        /// The top coordinate of the rubber band in device independent pixels.
        /// </value>
        public double Top
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the width of the rubber band.
        /// </summary>
        /// <value>
        /// The width of the rubber band in device independent pixels.
        /// </value>
        public double Width
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the height of the rubber band.
        /// </summary>
        /// <value>
        /// The height of the rubber band in device independent pixels.
        /// </value>
        public double Height
        {
            get;
            set;
        }

        #endregion
    }
}